#include "stdafx.h"
#include <sstream>
#include "HttpClient.h"
#include "text.hpp"
using namespace HeavenII::text;
#include <tchar.h>
#include <Windows.h>
#include <curl/curl.h>


namespace HeavenII
{
namespace net
{

//////////////////////////////////////////////////////////////////////////
//  global functions

static CURLSH *g_culShareHandle = nullptr;

void Initialize()
{
    curl_global_init(CURL_GLOBAL_WIN32 | CURL_GLOBAL_SSL);

    g_culShareHandle = curl_share_init();
    curl_share_setopt(g_culShareHandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
    curl_share_setopt(g_culShareHandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
}


void Cleanup()
{
    curl_share_cleanup(g_culShareHandle);
    curl_global_cleanup();
}


void SetShareHandle(CURL *curl)
{
    const long DNS_CACHE_TIMEOUT = 60 * 60;
    curl_easy_setopt(curl, CURLOPT_SHARE, g_culShareHandle);
    curl_easy_setopt(curl, CURLOPT_DNS_CACHE_TIMEOUT, DNS_CACHE_TIMEOUT);
}

//////////////////////////////////////////////////////////////////////////
// HttpPacket class

HttpPacket::HttpPacket()
    : m_pEntity(new std::string)
    , m_pHeaders(new STRSTRMAP)
{

}


HttpPacket::~HttpPacket()
{
    delete m_pEntity;
    delete m_pHeaders;
}


void HttpPacket::Clear()
{
    m_pHeaders->clear();
    m_pEntity->clear();
}


void HttpPacket::AddHeader(const char *pszName, const char *pszValue)
{
    m_pHeaders->insert(std::make_pair(pszName, pszValue));
}


void HttpPacket::AddHeader(const wchar_t *pszName, const wchar_t *pszValue)
{
    AddHeader(Utf16ToAscii(pszName).c_str(), Utf16ToAscii(pszValue).c_str());
}


void HttpPacket::SetHeaders(STRSTRMAP *pHeaders)
{
    m_pHeaders->clear();
    m_pHeaders->insert(pHeaders->cbegin(), pHeaders->cend());
}


void HttpPacket::EnumHeaders(std::function<void(const char *, const char *)> callback)
{
    for (auto iter : *m_pHeaders) {
        callback(iter.first.c_str(), iter.second.c_str());
    }
}


const char *HttpPacket::GetHeader(const char *pszName) const
{
    auto iter = m_pHeaders->find(pszName);
    if (iter != m_pHeaders->cend())
        return iter->second.c_str();
    else
        return nullptr;
}


void HttpPacket::SetEntity(const void *pEntity, size_t nSizeInWords)
{
    if (nSizeInWords != -1)
        m_pEntity->assign((const char *)pEntity, nSizeInWords);
    else
        m_pEntity->assign((const char *)pEntity);
}


const char *HttpPacket::GetEntity(size_t *pSizeInWords) const
{
    if (pSizeInWords) {
        *pSizeInWords = m_pEntity->size();
    }

    return m_pEntity->data();
}

//////////////////////////////////////////////////////////////////////////
// HttpWebRequest class

HttpWebRequest::HttpWebRequest(const char *pszUrl, RequestMethod method)
    : m_bAllowRedirect(true)
    , m_RequestMethod(method)
    
{
    strcpy_s(m_szRequestUrl, pszUrl);
}


HttpWebRequest::HttpWebRequest(const wchar_t *pszUrl, RequestMethod method)
    : m_bAllowRedirect(true)
    , m_RequestMethod(method)
{
    std::string url(Utf16ToAscii(pszUrl));
    strcpy_s(m_szRequestUrl, url.c_str());
}


HttpWebRequest::~HttpWebRequest()
{
}


const char *HttpWebRequest::GetRequestUrl() const
{
    return m_szRequestUrl;
}


void HttpWebRequest::EnableRedirect(bool bRedirect)
{
    m_bAllowRedirect = bRedirect;
}


bool HttpWebRequest::IsAllowRedirect() const
{
    return m_bAllowRedirect;
}


RequestMethod HttpWebRequest::GetMethod() const
{
    return m_RequestMethod;
}


void HttpWebRequest::SetMethod(RequestMethod method)
{
    m_RequestMethod = method;
}

//////////////////////////////////////////////////////////////////////////
// ChromeHttpWebRequest class

ChromeHttpWebRequest::ChromeHttpWebRequest(const char *pszUrl, RequestMethod method)
    : HttpWebRequest(pszUrl, method)
{
    if (EndsWith(pszUrl, ".json"))
        AddHeader("Accept", "application/json, text/javascript, */*; q=0.01");
    else if (strstr(pszUrl, ".htm"))
        AddHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
    else
        AddHeader("Accept", "*/*");
    AddHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36");
}


ChromeHttpWebRequest::ChromeHttpWebRequest(const wchar_t *pszUrl, RequestMethod method)
    : HttpWebRequest(pszUrl, method)
{
    AddHeader("Accept", "*/*");
    AddHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36");
}

//////////////////////////////////////////////////////////////////////////
// HttpWebResponse class

HttpWebResponse::HttpWebResponse()
{
    m_Encoding = Encoding::UNSET;
}


HttpWebResponse::HttpWebResponse(STRSTRMAP& headers, std::string& body, Encoding encoding)
{
    SetHeaders(&headers);
    SetEntity(body.data(), body.size());
    m_Encoding = encoding;
}


HttpWebResponse::~HttpWebResponse()
{
}


void HttpWebResponse::Clear()
{
    m_Encoding = Encoding::UNSET;
    __super::Clear();
}


Encoding HttpWebResponse::GetContentEncoding() const
{
    return m_Encoding;
}


void HttpWebResponse::SetContentEncoding(Encoding encoding)
{
    m_Encoding = encoding;
}


const char *HttpWebResponse::GetCookies() const
{
    STRSTRMAP::const_iterator result = m_pHeaders->find("Cookie");
    if (result != m_pHeaders->cend())
        return result->second.c_str();
    return nullptr;
}

//////////////////////////////////////////////////////////////////////////
// HttpClient class

HttpClient::HttpClient()
{

}


HttpClient::~HttpClient()
{

}


size_t HttpClient::OnWrite(char *pData, size_t size, size_t nmemb, std::string *pBufferStream)
{
    if (!pBufferStream)
        return 0;
    pBufferStream->append(pData, size * nmemb);
    return size * nmemb;
}


int HttpClient::GetTimeout() const
{
    return m_nTimeout;
}


void HttpClient::SetTimeout(int nMilliseconds)
{
    m_nTimeout = nMilliseconds;
}


int HttpClient::GetResponse(HttpWebRequest& request, HttpWebResponse *pResonse)
{
    std::stringstream buffer;
    char szErrorBuffer[CURL_ERROR_SIZE];
    CURL *curl = curl_easy_init();
    SetShareHandle(curl);
    if (!curl) {
        // TRACE("Failed to create connection");
        return -1;
    }

#if defined(_DEBUG)
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
    // set propxy from system configure
    DWORD dwRead = sizeof(DWORD);
    DWORD dwProxyEnabled = 0;
    DWORD dwType = REG_DWORD;
    HKEY hInetSettings;
    ::RegOpenKey(HKEY_CURRENT_USER, _T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"), &hInetSettings);
    ::RegGetValue(hInetSettings, nullptr, _T("ProxyEnable"), RRF_RT_REG_DWORD, nullptr, &dwProxyEnabled, &dwRead);
    if (dwProxyEnabled) {
        wchar_t szProxyBuffer[512] = { 0 };
        dwRead = sizeof(szProxyBuffer);
        ::RegGetValue(hInetSettings, nullptr, _T("ProxyServer"), RRF_RT_REG_SZ, nullptr, szProxyBuffer, &dwRead);
        // parse proxy server string
        const wchar_t *pHttpTrim = wcsstr(szProxyBuffer, L"http=");
        const wchar_t *pBegin = pHttpTrim + 5;
        const wchar_t *pEndPos = wcschr(pBegin, _T(';'));
        wchar_t szBuffer[512] = { 0 };
        wcsncpy_s(szBuffer, pBegin, pEndPos - pBegin);
        std::string strProxy(Utf16ToAscii(szBuffer));
        curl_easy_setopt(curl, CURLOPT_PROXY, strProxy.c_str());
    }
    ::RegCloseKey(hInetSettings);
#endif

    if (request.GetMethod() == RequestMethod::Post) {
        size_t nEntitySize;
        const char *pEntity = request.GetEntity(&nEntitySize);
        curl_easy_setopt(curl, CURLOPT_POST, 1);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, pEntity);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, nEntitySize);
    }
    curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
    // get headers from response
    std::string strResHeaders;
    curl_easy_setopt(curl, CURLOPT_HEADERDATA, &strResHeaders);
    curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, OnWrite);
    curl_easy_setopt(curl, CURLOPT_VERBOSE, 0);
    curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, szErrorBuffer);
    curl_easy_setopt(curl, CURLOPT_URL, request.GetRequestUrl());
    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, request.IsAllowRedirect());
    // enable cookies engine
    curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "");
    // set headers
    STRSTRMAP headers;
    //request.GetHeaders(headers);
    curl_slist *pHeaders = nullptr;
    request.EnumHeaders([&](const char *pszName, const char *pszValue) {
        buffer.str("");
        buffer << pszName << ": " << pszValue;
        pHeaders = curl_slist_append(pHeaders, buffer.str().c_str());
    });

    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, pHeaders);

    std::string contentBuffer;
    if (pResonse) {
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, OnWrite);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &contentBuffer);
    }

    if (CURLE_OK != curl_easy_perform(curl)) {
        //LOG4CPLUS_ERROR(m_Logger, "Perform failed: " << szErrorBuffer);
    }

    char *lpszContentType;
    curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &lpszContentType);
    Encoding encoding = GetEncodingFromContentType(lpszContentType);

    int nResCode;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &nResCode);

    if (pResonse && strResHeaders.size()) {
        STRSTRMAP resHeaders;
        STRSTRMAP resCookies;
        ParseHeaders(strResHeaders.c_str(), resHeaders);
        ParseCookies(strResHeaders.c_str(), resCookies);
        std::stringstream cookies;
        for (auto iter : resCookies) {
            cookies << iter.first << "=" << iter.second << "; ";
        }
        pResonse->SetHeaders(&resHeaders);
        pResonse->AddHeader("Cookie", cookies.str().c_str());
        pResonse->SetEntity(contentBuffer.data(), contentBuffer.size());
        pResonse->SetContentEncoding(encoding);
        // *lppResonse = new HttpWebResponse(resHeaders, resCookies, contentBuffer, encoding);
    }

    if (pHeaders)
        curl_slist_free_all(pHeaders);

    curl_easy_cleanup(curl);

    return nResCode;
}


Encoding HttpClient::GetEncodingFromContentType(const char *lpszContentType)
{
    if (!lpszContentType)
        return Encoding::UNSET;

    const char szToken[] = "charset=";
    const int nTokenLen = sizeof(szToken) - 1;
    const char *lpszBegin = strstr(lpszContentType, szToken);
    if (lpszBegin) {
        lpszBegin += nTokenLen;
        if (!_stricmp("gbk", lpszBegin)) {
            return Encoding::GBK;
        } else if (!_stricmp("utf-8", lpszBegin)) {
            return Encoding::UTF8;
        }
    }

    return Encoding::UTF8;
}


void HttpClient::ParseHeaders(const char *lpszHeaders, STRSTRMAP& headers)
{
    // find the begin position of the last packet.
    const char *pToken = "HTTP/1.";
    int nTokenLen = strlen(pToken);
    const char *pBeginPos = nullptr;
    const char *pNextPos = lpszHeaders;
    while (pNextPos = strstr(pNextPos, pToken)) {
        pBeginPos = pNextPos;
        pNextPos += nTokenLen + 1;  // strip a char of the subversion
    }
    // find the begin position of the packet.
    const char *lpszPacket = pBeginPos;
    const char *pFieldsBegin = strchr(lpszPacket, 0x0A);
    pFieldsBegin += 1;
    // parse fields
    bool isName = true;
    int nFieldsSize = strlen(pFieldsBegin) + 1;
    char *lpszFieldsBuffer = (char *)malloc(nFieldsSize);
    strcpy_s(lpszFieldsBuffer, nFieldsSize, pFieldsBegin);
    const char *lpszFieldName = nullptr;
    pBeginPos = lpszFieldsBuffer;
    pNextPos = pBeginPos;
    while (pNextPos && *pNextPos) {
        if (isName) {
            if (pNextPos = strchr(pNextPos, ':')) {
                lpszFieldName = pBeginPos;
                *const_cast<char *>(pNextPos) = 0;
                while (*(++pNextPos) == 0x20);
                pBeginPos = pNextPos;
                if (!strcmp(lpszFieldName, "Set-Cookie")) {
                    continue;
                }
            }
        } else {
            if (pNextPos = strchr(pNextPos, 0x0d)) {
                *const_cast<char *>(pNextPos) = 0;
                headers[lpszFieldName].assign(pBeginPos, pNextPos - pBeginPos);
                while (*(++pNextPos) == 0x0a);
                pBeginPos = pNextPos;
            }
        }
        isName = !isName;
    }
    free(lpszFieldsBuffer);
}


void HttpClient::ParseCookies(const char *lpszHeaders, STRSTRMAP& cookies)
{
    const int nHeadersLen = strlen(lpszHeaders) + 1;
    char *lpBuffer = (char *)malloc(nHeadersLen);
    strcpy_s(lpBuffer, nHeadersLen, lpszHeaders);

    const char token[] = "Set-Cookie: ";
    const int nTokenSize = sizeof(token) - 1;
    const char *lpBeginPos = lpBuffer;
    const char *lpCookieName = nullptr;
    const char *lpszCookieValue = nullptr;
    while (lpBeginPos = strstr(lpBeginPos, token)) {
        lpBeginPos += nTokenSize;
        lpCookieName = lpBeginPos;
        lpBeginPos = strchr(lpBeginPos, '=');
        *const_cast<char *>(lpBeginPos) = 0;
        lpszCookieValue = ++lpBeginPos;
        lpBeginPos = strchr(lpBeginPos, ';');
        *const_cast<char *>(lpBeginPos++) = 0;
        cookies[lpCookieName].assign(lpszCookieValue);
    }

    free(lpBuffer);
}

}
}
